package com.dede.applib

import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Looper
import android.widget.Toast
import com.dede.applib.util.ExitUtil
import com.dede.applib.util.d
import com.dede.applib.util.e
import java.io.*
import java.text.SimpleDateFormat
import java.util.*

/**
 * @author hsh
 * @date 2017/8/21 10:47.
 * @doc
 */
class CrashHandler : Thread.UncaughtExceptionHandler, com.dede.applib.util.LogExtends {

    private lateinit var mApplicationContext: Context

    //用来存储设备信息和异常信息
    private val deviceInfo = kotlin.collections.LinkedHashMap<String, String>()
    private val formatter = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")

    companion object {

        @SuppressLint("StaticFieldLeak")
        private var instance: CrashHandler? = null

        @Synchronized
        fun instance(): CrashHandler {
            if (instance == null)
                instance = CrashHandler()
//            d("instance")
            return instance!!
        }
    }

    private lateinit var defaultUncaughtExceptionHandler: Thread.UncaughtExceptionHandler

    fun init(application: Application) {
        d("init")
        mApplicationContext = application.applicationContext
        defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()//保存默认的异常处理器实例，兼容安卓8.0
        Thread.setDefaultUncaughtExceptionHandler(this)
    }

    override fun uncaughtException(t: Thread, e: Throwable?) {
        e("uncaughtException", e)
        if (Build.VERSION.SDK_INT >= 26) {
            defaultUncaughtExceptionHandler.uncaughtException(t, e)
        }
        handleException(e)

        try {
            Thread.sleep(3000)
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }
        //退出程序
        ExitUtil.finish()
        android.os.Process.killProcess(android.os.Process.myPid())
        System.exit(-1)
    }

    private fun handleException(ex: Throwable?) {
        if (ex == null) {
            return
        }

        //使用Toast来显示异常信息
        object : Thread() {
            override fun run() {
                Looper.prepare()
                Toast.makeText(mApplicationContext, "很抱歉,程序出现异常,即将退出！\n" +
                        "错误信息以保存至：${mApplicationContext.getExternalFilesDir("log").absolutePath}下",
                        Toast.LENGTH_SHORT).show()
                Looper.loop()
            }
        }.start()

        if (BuildConfig.DEBUG) {
            return
        }

        //收集设备参数信息
        collectDeviceInfo(mApplicationContext)

        //保存日志文件
        saveCatchInfo2File(ex)
    }

    /**
     * 保存日志文件，不需要读写SD卡权限
     */
    private fun saveCatchInfo2File(ex: Throwable) {
        val sb = StringBuilder()
        for ((key, value) in deviceInfo) {
            sb.append(key + "=" + value + "\n")
        }

        val writer = StringWriter()
        val printWriter = PrintWriter(writer)
        ex.printStackTrace(printWriter)
        var cause = ex.cause
        while (cause != null) {
            cause.printStackTrace(printWriter)
            cause = cause.cause
        }
        printWriter.close()
        val result = writer.toString()
        sb.append(result)
        var fos: FileOutputStream? = null
        try {
            val timestamp = System.currentTimeMillis()
            val time = formatter.format(Date())
            val fileName = "crash-$time-$timestamp.txt"
            val dir = mApplicationContext.getExternalFilesDir("log")
            fos = FileOutputStream(dir.absolutePath + File.separator + fileName)
            fos.write(sb.toString().toByteArray())
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            try {
                fos?.close()
            } catch (e: IOException) {
            }
        }
    }

    private fun collectDeviceInfo(context: Context) {
        try {
            val pm = context.packageManager
            val pi = pm.getPackageInfo(context.packageName, PackageManager.GET_ACTIVITIES)
            deviceInfo.put("versionName", pi.versionName)
            deviceInfo.put("versionCode", pi.versionCode.toString())

            val fields = Build::class.java.declaredFields
            fields.forEach {
                it.isAccessible = true
                val name = it.name
                val value = it.get(null)
                deviceInfo.put(name, value.toString())
                d(name + " : " + value)
            }

            val declaredFields = Build.VERSION::class.java.declaredFields
            declaredFields.forEach {
                it.isAccessible = true
                val name = it.name
                val value = it.get(null)
                deviceInfo.put(name, value.toString())
                d(name + " : " + value)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}